package org.maxgroundstation.groundstation.view.map;

import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;

import org.maxgroundstation.groundstation.GroundStation;
import org.maxgroundstation.groundstation.GroundStationEvent;
import org.maxgroundstation.groundstation.GroundStationListener;
import org.maxgroundstation.world.WorldModel;

/**
 * A map overlay for displaying the rover.
 * @author Judd Reed
 * 
 * Copyright (C) 2011  Scott Davis
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
public class BaseSceneMap {
    
    private MapViewPanel viewPanel;
    private WorldModel world = null;
    private BufferedImage imageCache = null;
    private String baseMapStyleCache = null;
    private GpsLocation currentLocCache = null;
    private int widthCache = -1;
    private int heightCache = -1;
    private float zoomCache = 0F;
    
    /**
     * Constructor
     * @param gs the ground station.
     */
    public BaseSceneMap(GroundStation gs, MapViewPanel viewPanel) {
        this.viewPanel = viewPanel;
        
        // Set world model when it is set for the ground station.
        gs.addGroundStationListener(new GroundStationListener() {

            @Override
            public void groundStationUpdate(GroundStationEvent event) {
                if (GroundStationEvent.WORLD_MODEL_SET.equals(event.getType())) {
                    GroundStation gs = (GroundStation) event.getSource();
                    setWorld(gs.getWorldModel());
                }
            }
        });
    }
    
    /**
     * Set the world model.
     * @param world the world model.
     */
    private void setWorld(WorldModel world) {
        this.world = world;
    }
    
    /**
     * Draws the map, rendering as required.
     * @param baseMapStyle the map style.
     * @param g2d the graphics context.
     * @param currentLoc the current map center location.
     * @param width the map width (pixels).
     * @param height the map height (pixels).
     * @param zoom the zoom level (meters/pixel)
     * @param fullRender true if map needs to be fully rendered.
     */
    public void drawMap(String baseMapStyle, Graphics2D g2d, GpsLocation currentLoc, 
            int width, int height, float zoom, boolean fullRender) {
        
        if (currentLoc != null) {
            // Check if map parameters cache is dirty.
            if (fullRender || isMapParameterCacheDirty(baseMapStyle, currentLoc, width, height, zoom)) {
                // Draw new fully rendered map.
                drawMapFullRender(baseMapStyle, g2d, currentLoc, width, height, zoom);
            }
            else {
                // Draw sliver rendered map.
                drawMapSliverRender(baseMapStyle, g2d, currentLoc, width, height, zoom);
            }
        }
	}
    
    /**
     * Checks if map parameters have changed from cache values.
     * @param baseMapStyle the base map style.
     * @param currentLoc the current GPS location.
     * @param width the map width.
     * @param height the map height.
     * @param zoom the map zoom level.
     * @return true if map parameters are dirty.
     */
    private boolean isMapParameterCacheDirty(String baseMapStyle, GpsLocation currentLoc, int width, 
            int height, float zoom) {
        boolean result = false;
        if (currentLocCache == null) result = true;
        else if (Math.abs(MapUtil.getXDiff(currentLocCache, currentLoc)) > (50D * zoom)) result = true;
        else if (Math.abs(MapUtil.getYDiff(currentLocCache, currentLoc)) > (50D * zoom)) result = true;
        if (!baseMapStyle.equals(baseMapStyleCache)) result = true;
        if (width != widthCache) result = true;
        if (height != heightCache) result = true;
        if (zoom != zoomCache) result = true;
        
        return result;
    }
    
    /**
     * Draw the map by fully rendering it.
     * @param baseMapStyle the map style.
     * @param g2d the graphics context.
     * @param currentLoc the map center location.
     * @param width the map width (pixels).
     * @param height the map height (pixels).
     * @param zoom the zoom level (meters/pixel).
     */
    public void drawMapFullRender(String baseMapStyle, Graphics2D g2d, GpsLocation currentLoc, 
            int width, int height, float zoom) {
        
        if ((currentLoc != null) && (world != null)) {
            imageCache = world.getMapImage(baseMapStyle, currentLoc.getLatitude(), 
                    currentLoc.getLongitude(), width, height, zoom);
        
            // Draw image.
            g2d.drawImage(imageCache, 0, 0, viewPanel);
        
            // Set cached parameters.
            currentLocCache = currentLoc;
            baseMapStyleCache = baseMapStyle;
            widthCache = width;
            heightCache = height;
            zoomCache = zoom;
        }
    }
    
    /**
     * Draw the map by using cached map and rending edge slivers as necessary.
     * @param baseMapStyle the map style.
     * @param g2d the graphics context.
     * @param currentLoc the map center location.
     * @param width the map width (pixels).
     * @param height the map height (pixels).
     * @param zoom the zoom level (meters/pixel).
     */
    private void drawMapSliverRender(String baseMapStyle, Graphics2D g2d, GpsLocation currentLoc, 
            int width, int height, float zoom) {
        
        if (currentLoc != null) {
            double xDiff = -1D * MapUtil.getXDiff(currentLocCache, currentLoc);
            double yDiff = MapUtil.getYDiff(currentLocCache, currentLoc);
            int pixelXDiff = (int) Math.round(xDiff / zoom);
            int pixelYDiff = (int) Math.round(yDiff / zoom);
            
            if ((pixelXDiff != 0) || (pixelYDiff != 0)) {
                // Draw cached image with x and/or y offset.
                BufferedImage newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
                Graphics2D newG2d = (Graphics2D) newImage.getGraphics();
                newG2d.drawImage(imageCache, pixelXDiff, pixelYDiff, viewPanel);
                
                if (pixelYDiff != 0) {
                    // Determine vertical center point of map sliver.
                    double sliverYCenter = ((height / 2D) - Math.abs(yDiff / 2D)) * zoom;
                    int startY = 0;
                    if (pixelYDiff < 0) {
                        sliverYCenter *= -1D;
                        startY = height + pixelYDiff;
                    }
                    GpsLocation verticalSliverLoc = MapUtil.getNewGpsLocation(0D, sliverYCenter, currentLoc);
                    
                    // Generate horizontal map sliver.
                    Image vertMapSliver = world.getMapImage(baseMapStyle, verticalSliverLoc.getLatitude(), 
                            verticalSliverLoc.getLongitude(), width, Math.abs(pixelYDiff), zoom);
                    newG2d.drawImage(vertMapSliver, 0, startY, viewPanel);
                }
                
                if (pixelXDiff != 0) {
                    // Generate and draw vertical map sliver.
                    double sliverXCenter = ((width / 2D) - Math.abs(xDiff / 2D)) * zoom * -1D;
                    int startX = 0;
                    if (pixelXDiff < 0) {
                        sliverXCenter *= -1D;
                        startX = width + pixelXDiff;
                    }
                    GpsLocation horizontalSliverLoc = MapUtil.getNewGpsLocation(sliverXCenter, 0D, currentLoc);
                    
                    // Generate vertical map sliver.
                    Image horizMapSliver = world.getMapImage(baseMapStyle, horizontalSliverLoc.getLatitude(), 
                            horizontalSliverLoc.getLongitude(), Math.abs(pixelXDiff), height, zoom);
                    newG2d.drawImage(horizMapSliver, startX, 0, viewPanel);
                }
                
                imageCache = newImage;
                g2d.drawImage(imageCache, 0, 0, viewPanel);
                
                // Update currentLocCache
                double newLatitude = currentLocCache.getLatitude();
                if (Math.abs(pixelYDiff) > 0) newLatitude = currentLoc.getLatitude();
                double newLongitude = currentLocCache.getLongitude();
                if (Math.abs(pixelXDiff) > 0) newLongitude = currentLoc.getLongitude();
                currentLocCache = new GpsLocation(newLatitude, newLongitude);
            }
            else {
                // Draw cached map.
                drawCachedMap(g2d);
            }
        }
    }
    
    /**
     * Draw the map using the cached map.
     * @param g2d the graphics context.
     */
    private void drawCachedMap(Graphics2D g2d) {
        
        if (imageCache != null) {
            // Draw cached image.
            g2d.drawImage(imageCache, 0, 0, viewPanel);
        }
    }
}